using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using SynapseGaming.LightingSystem.Core;
using SynapseGaming.LightingSystem.Editor;
using SynapseGaming.LightingSystem.Lights;
using SynapseGaming.LightingSystem.Rendering;

using Jitter;
using Jitter.Collision;
using Jitter.Collision.Shapes;
using Jitter.Dynamics;
using Jitter.Forces;
using Jitter.LinearMath;

namespace SunburnJitter
{
    public static class _JitterExtensions
    {
        public static Vector3 ToVector3(this JVector vec)
        {
            return new Vector3(vec.X, vec.Y, vec.Z);
        }
        public static JVector ToJVector(this Vector3 vec)
        {
            return new JVector(vec.X, vec.Y, vec.Z);
        }
        public static Matrix ToXNAMatrix(this JMatrix matrix)
        {
            return new Matrix(matrix.M11,
                            matrix.M12,
                            matrix.M13,
                            0.0f,
                            matrix.M21,
                            matrix.M22,
                            matrix.M23,
                            0.0f,
                            matrix.M31,
                            matrix.M32,
                            matrix.M33,
                            0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
        }

        public static JMatrix ToJitterMatrix(this Matrix matrix)
        {
            JMatrix result;
            result.M11 = matrix.M11;
            result.M12 = matrix.M12;
            result.M13 = matrix.M13;
            result.M21 = matrix.M21;
            result.M22 = matrix.M22;
            result.M23 = matrix.M23;
            result.M31 = matrix.M31;
            result.M32 = matrix.M32;
            result.M33 = matrix.M33;
            return result;

        }
    }

    /// <summary>
    /// Manages all the collision stuff of Jitter
    /// </summary>
    public class JitterCollisionManager : IUpdatableManager, IManagerService
    {
        internal static JitterCollisionManager Instance;   //"internal singleton", in order to provide a simpler interface

        public World World { get; private set; }
        public Vector3 Gravity { get { return World.Gravity.ToVector3(); } set { World.Gravity = value.ToJVector(); } }
        public bool IsMultithreaded { get; set; }
        public bool DebugDrawAll { get; set; }
        /// <summary>
        /// The transparancy of debug drawn objects, 0 for completly transparent, 1 for solid
        /// </summary>
        public float DebugDrawTransparancy { get; set; }

        internal SceneInterface SceneInterface { get; private set; }
        private enum Primitives { box, sphere, cylinder, cone, capsule }

        private Primitives3D.GeometricPrimitive[] primitives =
            new Primitives3D.GeometricPrimitive[5];
        private BasicEffect effect; //used for debug drawing

        private BlendState blendState = new BlendState { AlphaSourceBlend = Blend.One, AlphaDestinationBlend = Blend.InverseSourceAlpha, ColorSourceBlend = Blend.SourceAlpha, ColorDestinationBlend = Blend.InverseSourceAlpha };

        public JitterCollisionManager(SceneInterface sceneInterface)
        {
            Instance = this;
            this.SceneInterface = sceneInterface;
            World = new World(new CollisionSystemSAP());
            ManagerProcessOrder = 10;
            IsMultithreaded = true;
            Gravity = new Vector3(0, -9.81f, 0);
            DebugDrawTransparancy = 0.7f;
        }

        /// <summary>
        /// Call this after the graphics device has been initialized
        /// </summary>
        public void Initialize()
        {
            primitives[(int)Primitives.box] = new Primitives3D.BoxPrimitive(SceneInterface.GraphicsDeviceManager.GraphicsDevice);
            primitives[(int)Primitives.capsule] = new Primitives3D.CapsulePrimitive(SceneInterface.GraphicsDeviceManager.GraphicsDevice);
            primitives[(int)Primitives.cone] = new Primitives3D.ConePrimitive(SceneInterface.GraphicsDeviceManager.GraphicsDevice);
            primitives[(int)Primitives.cylinder] = new Primitives3D.CylinderPrimitive(SceneInterface.GraphicsDeviceManager.GraphicsDevice);
            primitives[(int)Primitives.sphere] = new Primitives3D.SpherePrimitive(SceneInterface.GraphicsDeviceManager.GraphicsDevice);

            effect = new BasicEffect(SceneInterface.GraphicsDeviceManager.GraphicsDevice);
        }

        /// <summary>
        /// Submits a scene to the collision system, this scene is generated as triangle mesh.
        /// </summary>
        /// <param name="scene"></param>
        public void SubmitStatic(IScene scene)
        {
            foreach (ISceneEntityGroup group in scene.EntityGroups)
            {
                foreach (ISceneEntity entity in group.Entities)
                {
                    entity.Components.Add(new TriangleMeshRigidBodyComponent());
                }
            }
        }

        internal Shape ConstructTriangleMesh(ISceneEntity entity)
        {
            entity.HullType = HullType.Mesh;
            entity.ObjectType = ObjectType.Static;
            (entity as SceneObject).CollisionType = SynapseGaming.LightingSystem.Collision.CollisionType.Collide;

            Vector3 pos = GetPos(entity);

            if (!(entity is SceneObject))
                throw new Exception("Failed to construct triangle mesh, entity is not a scene object!");
            List<Vector3> vertices = new List<Vector3>();
            List<JOctree.TriangleVertexIndices> indices = new List<JOctree.TriangleVertexIndices>();

            List<JVector> jVertices = new List<JVector>();

            SynapseGaming.LightingSystem.Collision.CollisionMesh mesh = (entity as SceneObject).WorldCollisionMesh;
            for (int i = 0; i < mesh.Indices.Length; i += 3)
            {
                indices.Add(new JOctree.TriangleVertexIndices(mesh.Indices[i], mesh.Indices[i + 1], mesh.Indices[i + 2]));
            }
            foreach (Vector3 v in mesh.Vertices)
                jVertices.Add(Vector3.Transform(v - pos, Matrix.Identity).ToJVector());

            Shape shape = new TriangleMeshShape(new JOctree(jVertices, indices));
            
            return shape;
        }

        public void ApplyPreferences(ILightingSystemPreferences preferences) { }

        /// <summary>
        /// Removes all physic objects from the scene.
        /// </summary>
        public void Clear()
        {
            World.Clear();
        }

        public void Unload() { }


        public void Update(GameTime gameTime)
        {
            float step = (float)gameTime.ElapsedGameTime.TotalSeconds;
            step = Math.Min(1.0f / 100.0f, step);

            World.Step(step, IsMultithreaded);
        }

        /// <summary>
        /// Draws all debug information to the screen.
        /// </summary>
        /// <param name="view">The view matrix of the camera.</param>
        /// <param name="proj">The projection matrix of the camera.</param>
        public void Draw(Matrix view, Matrix proj)
        {
            //Fill list:
            foreach (RigidBody body in World.RigidBodies)
            {
                if (!(body.Tag is RigidBodyComponent))
                    continue;
                if ((body.Tag as RigidBodyComponent).DebugDraw || DebugDrawAll)
                    AddBodyToDrawList(body);
            }

            effect.View = view;
            effect.Projection = proj;
            effect.DiffuseColor = Color.LightGray.ToVector3();
            effect.Alpha = DebugDrawTransparancy;

            BlendState prevBlendState = SceneInterface.GraphicsDeviceManager.GraphicsDevice.BlendState;
            SceneInterface.GraphicsDeviceManager.GraphicsDevice.BlendState = blendState;

            foreach (Primitives3D.GeometricPrimitive prim in primitives)
                prim.Draw(effect);

            SceneInterface.GraphicsDeviceManager.GraphicsDevice.BlendState = prevBlendState;
        }

        private void AddShapeToDrawList(Shape shape, JMatrix ori, JVector pos)
        {
            Primitives3D.GeometricPrimitive primitive = null;
            Matrix scaleMatrix = Matrix.Identity;

            if (shape is BoxShape)
            {
                primitive = primitives[(int)Primitives.box];
                scaleMatrix = Matrix.CreateScale((shape as BoxShape).Size.ToVector3());
            }
            else if (shape is SphereShape)
            {
                primitive = primitives[(int)Primitives.sphere];
                scaleMatrix = Matrix.CreateScale((shape as SphereShape).Radius);
            }
            else if (shape is CylinderShape)
            {
                primitive = primitives[(int)Primitives.cylinder];
                CylinderShape cs = shape as CylinderShape;
                scaleMatrix = Matrix.CreateScale(cs.Radius, cs.Height, cs.Radius);
            }
            else if (shape is CapsuleShape)
            {
                primitive = primitives[(int)Primitives.capsule];
                CapsuleShape cs = shape as CapsuleShape;
                scaleMatrix = Matrix.CreateScale(cs.Radius * 2, cs.Length, cs.Radius * 2);

            }
            else if (shape is ConeShape)
            {
                ConeShape cs = shape as ConeShape;
                scaleMatrix = Matrix.CreateScale(cs.Radius, cs.Height, cs.Radius);
                primitive = primitives[(int)Primitives.cone];
            }

            if (primitive == null)
                return;
            primitive.AddWorldMatrix(scaleMatrix * ori.ToXNAMatrix() *
                        Matrix.CreateTranslation(pos.ToVector3()));
        }

        private void AddBodyToDrawList(RigidBody rb)
        {
            //if (rb.Tag is BodyTag && ((BodyTag)rb.Tag) == BodyTag.DontDrawMe) return;

            bool isCompoundShape = (rb.Shape is CompoundShape);

            if (!isCompoundShape)
            {
                AddShapeToDrawList(rb.Shape, rb.Orientation, rb.Position);
            }
            else
            {
                CompoundShape cShape = rb.Shape as CompoundShape;
                JMatrix orientation = rb.Orientation;
                JVector position = rb.Position;

                foreach (var ts in cShape.Shapes)
                {
                    JVector pos = ts.Position;
                    JMatrix ori = ts.Orientation;

                    JVector.Transform(ref pos, ref orientation, out pos);
                    JVector.Add(ref pos, ref position, out pos);

                    JMatrix.Multiply(ref ori, ref orientation, out ori);

                    AddShapeToDrawList(ts.Shape, ori, pos);
                }

            }

        }

        private Vector3 GetScale(ISceneEntity entity)
        {
            Vector3 scale;
            Quaternion rot;
            Vector3 pos;
            entity.World.Decompose(out scale, out rot, out pos);
            return scale;
        }
        private Vector3 GetPos(ISceneEntity entity)
        {
            Vector3 scale;
            Quaternion rot;
            Vector3 pos;
            entity.World.Decompose(out scale, out rot, out pos);
            return pos;
        }
        private Quaternion GetOrientation(ISceneEntity entity)
        {
            Vector3 scale;
            Quaternion rot;
            Vector3 pos;
            entity.World.Decompose(out scale, out rot, out pos);
            return rot;
        }

        public int ManagerProcessOrder { get; set; }

        public Type ManagerType
        {
            get { return typeof(JitterCollisionManager); }
        }

    }
}
